#!/usr/bin/env node
// One-click build script (Windows-first, works with pnpm or npm)
// Usage from app/:
//   node ..\scripts\build-oneclick.mjs --green
//   node ..\scripts\build-oneclick.mjs --portable
//   node ..\scripts\build-oneclick.mjs --installer
// 说明：构建前会智能清理与本项目相关的 Electron/AutoTools/Playwright 浏览器进程，不会杀系统 Chrome。

import { spawn } from 'node:child_process'
import { existsSync } from 'node:fs'
import { rm } from 'node:fs/promises'
import path from 'node:path'
import os from 'node:os'
import { fileURLToPath } from 'node:url'

// Resolve current script directory for invoking helper PowerShell scripts
const __thisFile = fileURLToPath(import.meta.url)
const __thisDir = path.dirname(__thisFile)

function run(cmd, args, opts = {}) {
  return new Promise((resolve, reject) => {
    const child = spawn(cmd, args, {
      stdio: 'inherit',
      shell: process.platform === 'win32',
      cwd: opts.cwd || process.cwd(),
      env: { ...process.env, ...(opts.env || {}) },
    })
    child.on('close', (code) => {
      if (code === 0) resolve()
      else reject(new Error(`${cmd} exited with code ${code}`))
    })
  })
}

function runCapture(cmd, args, opts = {}) {
  return new Promise((resolve, reject) => {
    const child = spawn(cmd, args, {
      stdio: ['ignore', 'pipe', 'pipe'],
      shell: process.platform === 'win32',
      cwd: opts.cwd || process.cwd(),
      env: { ...process.env, ...(opts.env || {}) },
    })
    let stdout = ''
    let stderr = ''
    child.stdout.on('data', (d) => { stdout += d.toString() })
    child.stderr.on('data', (d) => { stderr += d.toString() })
    child.on('close', (code) => {
      if (code === 0) resolve({ stdout, stderr, code })
      else reject(new Error(stderr || `${cmd} exited with code ${code}`))
    })
  })
}

async function listProcesses() {
  if (process.platform !== 'win32') return []
  try {
    const ps1 = path.resolve(__thisDir, 'get-procs.ps1')
    const args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ps1]
    const { stdout } = await runCapture('powershell', args)
    if (!stdout?.trim()) return []
    const data = JSON.parse(stdout)
    if (Array.isArray(data)) return data
    if (data && typeof data === 'object') return [data]
    return []
  }
  catch {
    return []
  }
}

async function killResidualProcessesSmart({ repoRoot, appDir }) {
  if (process.platform !== 'win32') return
  const repoRootLower = (repoRoot || '').toLowerCase()
  const appDirLower = (appDir || '').toLowerCase()
  const browsersDirLower = path.join(appDir, 'browsers').toLowerCase()

  const targets = await listProcesses()
  const toKill = []
  for (const p of targets) {
    const name = (p?.Name || '').toLowerCase()
    const pid = p?.ProcessId
    const exe = (p?.ExecutablePath || '').toLowerCase()
    const cmd = (p?.CommandLine || '').toLowerCase()
    const line = `${exe} ${cmd}`
    const isFromRepo = line.includes(repoRootLower) || line.includes(appDirLower)
    if (!pid || !name) continue
    if (name === 'electron.exe' || name === 'autotools.exe') {
      if (isFromRepo) toKill.push({ pid, name, reason: 'match repo path' })
      continue
    }
    if (name === 'chromium.exe' || name === 'headless_shell.exe') {
      if (line.includes(browsersDirLower)) toKill.push({ pid, name, reason: 'match browsers dir' })
      continue
    }
  }

  if (!toKill.length) {
    console.log('[oneclick] smart-kill: no local processes matched')
    return
  }

  const list = toKill.map(t => `${t.name}#${t.pid}(${t.reason})`).join(', ')
  console.log('[oneclick] smart-kill targets:', list)
  for (const k of toKill) {
    try { await run('taskkill', ['/PID', String(k.pid), '/F'], { cwd: process.cwd() }) }
    catch {}
  }
}

async function removeIfExists(p) {
  try {
    if (existsSync(p)) await rm(p, { recursive: true, force: true })
  }
  catch {}
}

function pickPackageManager() {
  return new Promise((resolve) => {
    const test = spawn('pnpm', ['-v'], { stdio: 'ignore', shell: process.platform === 'win32' })
    test.on('error', () => resolve('npm'))
    test.on('close', (code) => resolve(code === 0 ? 'pnpm' : 'npm'))
  })
}

async function main() {
  const args = new Set(process.argv.slice(2))
  const mode = args.has('--green') ? 'green' : args.has('--portable') ? 'portable' : 'installer'

  // 切换至 app/ 目录（使用 fileURLToPath 规避 Windows 下 /C:/ 前缀导致的 C:\C:\ 路径问题）
  const __filename = fileURLToPath(import.meta.url)
  const __dirname = path.dirname(__filename)
  const appDir = path.resolve(__dirname, '..', 'app')
  process.chdir(appDir)

  console.log('[oneclick] cwd =', process.cwd())

  // 构建前进行智能清理（不影响系统 Chrome）
  const repoRoot = path.resolve(__dirname, '..')
  await killResidualProcessesSmart({ repoRoot, appDir })
  await removeIfExists(path.join(process.cwd(), 'dist-release'))
  await removeIfExists(path.join(process.cwd(), 'dist-release-portable'))

  const pm = await pickPackageManager()
  console.log(`[oneclick] using ${pm} install`)
  await run(pm, ['install'])

  const script = mode === 'green' ? 'build:green' : mode === 'portable' ? 'build:green:portable' : 'build:win'
  console.log(`[oneclick] run ${script}`)
  await run(pm, ['run', script])

  console.log('[oneclick] done')
}

main().catch((err) => {
  console.error('[oneclick] failed:', err.message || String(err))
  process.exit(1)
})


